HAL data structure and types

HAL peripherals instances

The HAL peripheral instances are represented as enumeration types. Each HAL PPP driver includes an enumeration type that lists the supported instances for the specific driver within the given series. These enumeration values for HAL PPP instances correspond to the respective CMSIS peripheral physical instances. This helps debugging and ensures the right types when selecting the instance.

  • HAL driver instances

typedef enum
{
  HAL_PPP1 =  (uint32_t)PPP1,
  HAL_PPP2 =  (uint32_t)PPP2,
  (...)
#if defined (PPPm)
  HAL_PPPm = (uint32_t)PPPm,
#endif
  (...)
  HAL_PPPn = (uint32_t)PPPn,
} hal_ppp_t;
  • CMSIS device instances

/* stm32tnabxx.h: a CMSIS device of the series  */
#define PPP1   ((PPP_TypeDef *)  PPP1_BASE)
#define PPP2   ((PPP_TypeDef *)  PPP2_BASE)
(...)
#define PPPn   ((PPP_TypeDef *)  PPPn_BASE)

When an instance is available only on certain devices within a given series, the corresponding HAL instance is enclosed by a compilation switch. This switch corresponds to the CMSIS peripheral physical instance as defined in the CMSIS header files for those specific devices.

No changes are required for the LL drivers; they continue using the HW PPP instances as defined in the CMSIS device.

Within the HAL_PPP_Init function, the instance parameter is of type hal_ppp_t and is stored in the corresponding instance parameter of the handle, with a check using an assert (even if the parameter is an enumeration).

Within the configuration or process functions, a cast is performed to transform the HAL instance to the CMSIS instance (which is a pointer to the CMSIS PPP_TypeDef structure), allowing access to the registers to read or write values.

Two methods are possible:

  • Using a local variable:

    /* stm32tnxx_hal_ppp.c */
    #define PPP_GET_INSTANCE(handle) ((PPP_TypeDef *)((uint32_t)(handle)->instance))
    
    hal_status_t HAL_PPP_Func(hal_ppp_handle_t hppp, ...)
    {
       PPP_TypeDef *p_pppx;
       p_pppx = PPP_GET_INSTANCE(handle);
       LL_PPP_Func(p_pppx, ...);
       (...)
    }
    
  • Using a direct conversion when calling the LL or/and accessing a register:

    /* stm32tnxx_hal_ppp.c */
    #define PPP_GET_INSTANCE(handle) ((PPP_TypeDef *)((uint32_t)(handle)->instance))
    
    hal_status_t HAL_PPP_Func(hal_ppp_handle_t hppp, ...)
    {
      LL_PPP_Func(PPP_GET_INSTANCE(hppp), ...);
      (...)
    }
    

Note

When converting from a HAL PPP instance, which is an enumeration, to a pointer to the CMSIS instance, an intermediate cast to uint32_t is mandatory. This step is necessary to avoid violating MISRA-C rule 11.7, which prohibits casting a non-integer type to a pointer.

HAL peripherals handles

Almost all HAL drivers are based on handle objects, as described in the HAL data structure section. These handles, denoted as hal_ppp_handle_t, are used to maintain the instance context throughout all HAL processes for a given peripheral instance. When a peripheral HAL driver is built around a handle, this object is allocated and exists in RAM resources for the lifetime.

The HAL handle object is allocated by the user at the application level; however, its content is fully managed by the corresponding HAL driver. This means that the user should not access the handle parameters directly, nor is it necessary to do so to initialize the HAL driver or start a process. All handle fields are private and should not be accessed directly for reading or writing by the application. The application relies entirely on the HAL driver APIs to configure the driver. Configuration parameters should be explicitly passed to the HAL driver through the dedicated configuration APIs. When necessary, the HAL handle can store some of these parameters for use during various process APIs.

All HAL drivers provide a peripheral handle, except for shared and system peripherals, where no handle or instance object is used. The peripherals concerned by this exception are:

  • GPIO

  • SYSTICK

  • NVIC

  • PWR

  • RCC

  • CORTEX (SYSTICK, NVIC, MPU, SCB)

  • RTC/TAMP

  • GTZC

  • DBGMCU

  • SYSCFG/SBS

  • RAMCFG/RAMECC

  • Flash Interface (handling the Flash OB and control, note that the Flash driver handling the program and erase processes remains using a handle)

The HAL peripheral handle should be defined in stm32tnxx_hal_ppp.h and used for the following purposes:

  • Multi-instance support: Each peripheral hardware instance has its own handle, ensuring no dependency between hardware instance resources.

  • Peripheral process intercommunication: The handle is used to maintain shared data resources between process routines, such as global pointers, counters, DMA handles, and state machines.

  • Internal variables and pointers storage: The handle is also used to store global variables and pointers within a HAL PPP driver.

  • Storage of user data pointer: The handle provides a void pointer to associate application-specific user data with the HAL PPP handle. Dedicated Set/Get user data APIs are provided.

Note

For RTC and TAMP, the HAL does not provide a handle, so no HAL instance parameter is provided. The HAL implementation of RTC and TAMP directly uses the CMSIS instance of these peripherals to read and write the appropriate registers. The LL RTC and TAMP continue providing the CMSIS instance as parameters.

typedef struct hal_ppp_handle_s hal_ppp_handle_t;

#if defined (USE_HAL_PPP_REGISTER_CALLBACK) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
typedef  void (*hal_ppp_cb_t)(hal_ppp_handle_t * hppp); /*!< pointer to a PPP callback function */
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS */

struct hal_ppp_handle_s
{
  hal_ppp_t instance; /*!< Peripheral instance */
  volatile  hal_ppp_state_t global_state; /*!< PPP global state */
  /* in case of several subinstances are available and can be active in parallel:
      Ex TIM Channels, channel_states[NB_CHANNELS]  */
  volatile  subinstance_state_t subinstance_states[nb_subinstance];
  /* Or in case of one single subinstance that can be active at a time:
    single subinstance state variable, Ex ADC Channel, channel_State */
  volatile  subinstance_state_t subinstance_state; /*!< PPP {subinstance} state */
 /* Or in case of multi process that can run in parallel, one state per process:
    example UART TxState and RxState */
  volatile  process_i_state_t process_i_state; /*!< PPP {process_i} state */

  volatile uint32_t param_i; /*!< Internal parameters */
  (...)
#if defined (USE_HAL_PPP_GET_LAST_ERRORS) && (USE_HAL_PPP_GET_LAST_ERRORS == 1)
/* in case of single process at a time: one single variable storing the last errors */
volatile uint32_t last_error_codes;
/* in  in case of several processes that can be active in parallel:
    one variable storing the last errors per process */
volatile uint32_t last_{process_i}_error_codes;
#endif /* USE_HAL_PPP_GET_LAST_ERRORS */

#if defined (USE_HAL_PPP_DMA) && (USE_HAL_PPP_DMA == 1)
  hal_dma_handle_t *hdma_i; /*!< Pointer to a specific process i DMA handle */
#endif /* USE_HAL_PPP_DMA  */

#if defined (USE_HAL_PPP_USER_DATA) && (USE_HAL_PPP_USER_DATA == 1)
  const void *p_user_data; /*!< User Data Pointer */
#endif /* USE_HAL_PPP_USER_DATA */

#if defined (USE_HAL_PPP_REGISTER_CALLBACK) && (USE_HAL_PPP_REGISTER_CALLBACKS == 1)
hal_ppp_cb_t p_event_i_cb;  /*!< PPP event i callback */
hal_ppp_cb_t p_error_cb;    /*!< Error callback */
#endif /* USE_HAL_PPP_REGISTER_CALLBACKS */
};

Field

Description

instance

Peripheral HAL hardware instance: one of the values defined in hal_ppp_t enumeration type

global_state

Peripheral global state

{sub_instance}_state

Table of the sub_instances states. Applicable when several sub_instances are available and can be active in parallel (Ex: TIMER channels) Or Sub_instance state. Applicable when several sub_instances are available but only one can be active at a time (Ex: the ADC channels)

{process_i}_state

Process state. Applicable when several processes can run in parallel. Ex UART tx_State and rx_State

param_i

Internal parameter(s): local data pointer, data counter…etc. Used to store some required info needed by the processes (Ex: data pointer and counter)

last_error_codes

Applicable when only a single process at a time, this variable stores the last occurred errors within the last executed process

last_{process_i}_error_codes

Applicable when several processes can run in parallel, one variable per process to store the last occurred errors within the executed process_i

hdma_i

Pointer to a specific process i DMA handle

Note: DMA fields and dedicated HAL PPP APIs are delimited by the USE_HAL_DMA_MODULE (former HAL_DMA_MODULE_ENABLED) allowing to reduce footprint and break the dependency with the HAL DMA driver when no HAL_PPP_Process_DMA are used by the application

p_user_data

Pointer to const void, used to allow the application associating specific user data to the handle by means of dedicated Set/Get user data APIs

p_event_i_cb

PPP event i callback: a callback that can be registered by user and triggered when the corresponding “event i” is reached asynchronously in case of DMA or IT model (Ex: tx_cplt_callback)

p_error_cb

A callback that can be registered by user and triggered when an asynchronous error is encountered (DMA or IT)

Note

The callback pointers are initialized to the weak empty callback during the HAL_PPP_Init. Also, the register callback APIs check that the callbacks are not null. As there is no check in the HAL_PPP_IRQHandler, the callback pointers are never set to null by any other means (for example, forcing the pointer stored inside the handle to null).

The HAL handle is declared as a structure hal_ppp_handle_s, followed by the declaration of hal_ppp_handle_t typedef. This approach allows the hal_ppp_handle_t type to be used within the handle for callbacks.

HAL configuration structures

The handle object should no longer store any Init structure as per the HAL1. Instead, only the necessary inter-process variables are stored within the handle.

Context: delta versus HAL1:

The HAL_PPP_InitTypeDef structure is converted to hal_ppp_config_t, which is provided as a parameter for the HAL_PPP_SetConfig and HAL_PPP_GetConfig APIs (used to apply and retrieve a configuration, respectively).

The hal_ppp_config_t, also known as the global configuration structure, provides the essential parameters needed to start a process. Depending on the peripheral, a process can be initiated immediately after applying the global configuration (e.g., UART) or may require additional sub-block configuration (e.g., TIM requires a global configuration and may also require at least one channel configuration if used in output or input compare mode).

In such cases, a sub-block configuration structure, hal_ppp_{subblock}_{subinstance}_config_t, is provided (e.g., hal_tim_oc_channel_config_t). This sub-block configuration structure is intended to be used with the sub-block Set/Get configuration APIs, HAL_PPP_{SUBBLOCK}_SetConfig{Subinstance} and HAL_PPP_{SUBBLOCK}_GetConfig{Subinstance} (e.g., HAL_TIM_OC_SetConfigChannel).

When additional features are provided by the peripheral that require configuration to control the behavior or/and a specific feature of the peripheral, a dedicated feature configuration structure is provided as hal_ppp_{feature}_config_t (e.g., hal_tim_deadtime_config_t, hal_spi_autonomousmode_config_t).

  • Example of global configuration structure

    typedef struct
    {
      uint32_t               prescaler;
      hal_tim_counter_mode_t counter_mode;
      uint32_t               period;
      hal_tim_clk_division_t clock_division;
      uint32_t               repetition_counter;
      hal_tim_clock_sel_t    clock_sel;
    } hal_tim_config_t;
    
  • Example of a sub-block configuration structure

    typedef struct
    {
      hal_tim_oc_mode_t mode;
      uint32_t          pulse;
    } hal_tim_oc_compare_unit_config_t;
    
  • Example of a feature configuration structure

    typedef struct
    {
      uint32_t                      crc_polynomial;
      hal_spi_crc_length_t          crc_length;
      hal_spi_crc_tx_init_pattern_t crc_tx_init_pattern;
      hal_spi_crc_rx_init_pattern_t crc_rx_init_pattern;
    } hal_spi_crc_config_t;
    

HAL parameter types

  • Finite params (including PPP instances) are provided as enumeration types at HAL levels.

    • For HAL drivers, the defines lists used for user parameters are provided as enumerations.

    • The numerical values correspond to the mask to be written or read in/from the corresponding register (through the corresponding LL define or the CMSIS bit field defines when no LL is provided for the given peripheral).

    • This applies for HAL layer only, the LL remains using defines as it is close to the HW CMSIS devices header files.

    • Within functions and structures that are using these parameters, the given enumeration type is used instead of a UINT generic type.

  • When the parameter (that is an enumeration type) is used as input (Ex: HAL_PPP_SetConfig case) it is used to read/modify/write the corresponding register.

    • An additional cast (to uint32_t) is required to avoid compilation warning.

    • Same when HAL is based on the LL.

  • When the parameter (that is an enumeration type) is used as output (Ex: HAL_PPP_GetConfig case):

    • An additional cast (to the enum type) is required to avoid compilation warning.

  • MISRA-C 2012 warning will be issued (R10.8 & R10.5) in any case, as we are setting an enumeration variable from a U32 (which theoretically can take values not defined in the enumeration).

    Note

    Waivers are granted automatically by the eQA in this case to avoid costly if/else or switch/case implementations.

  • When the parameters are intended to be provided to an API as a logical “OR” combination, it is a numerical type (do not use enumerations).

  • The HAL PPP driver provides an enumeration type listing the PPP instances supported by the given driver on the given series.

  • The LL drivers use the HW PPP instances as defined in the CMSIS device.

  • In both cases, HAL enumeration or HAL defines list used for HW registers access, values are redirected to the equivalent LL defines. This allows direct usage of the LL APIs.

  • All the user parameters are checked using asserts at HAL API implementation level; this applies regardless of the parameter type (enumeration types or numerical).

Example Image

HAL callback types

A dedicated type, hal_ppp_cb_t, is provided for HAL PPP callbacks. This type is to be used within the handle and within the HAL_PPP_RegisterXXXCallbacks APIs.

For some drivers, it is possible to have different callbacks with different prototypes. In such cases, a callback type for each prototype should be provided.

For example, in the case of HAL TIM:

#if defined (USE_HAL_TIM_REGISTER_CALLBACKS) && (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
/** HAL TIM generic callback pointer definition */
typedef void (* hal_tim_cb_t) (hal_tim_handle_t *);

/** HAL TIM callback pointer definition with channel parameter */
typedef void (* hal_tim_channel_cb_t) (hal_tim_handle_t *, hal_tim_channel_t);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */